package com.nulldev.util.other;

import java.io.FileInputStream;
import java.security.SecureRandom;

import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.MD5Utils;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.os.OSUtil;

public class RandomUtils {

	public static SecureRandom secure() {
		return new SecureRandom(ByteUtils.randomBytes(64));
	}
	
	private static boolean canUseSystemRNG() {
		return (FileUtil.isFile("/dev/urandom") || FileUtil.isFile("/dev/random"));
	}
	
	public static class nRandom {
		
		private final byte[] seed;
		
		public nRandom() {
			this(null);
		}
		
		public nRandom(final byte[] seed) {
			if (seed != null) {
				this.seed = seed;
			}else {
				this.seed = read(32);
			}
		}
		
		public byte[] read(final int length) {
			if (canUseSystemRNG()) {
				try {
					final FileInputStream fis = new FileInputStream(FileUtil.safeCreate("/dev/urandom"));
					byte[] data = new byte[length];
					fis.read(data);
					fis.close();
					return data;
				}catch (Exception e) {}
			}
			// fallback to Java Random
			return ByteUtils.randomBytes(length);
		}
		
		protected long nextL(final int bits) {
			return (MathUtil.getLong(read(8), 0) >>> (64 - bits));
		}
		
		protected int next(final int bits) {
			return (int) (MathUtil.getInt(read(4), 0) >>> (48 - bits));
		}
		
		public long makeLong() {
			return nextL(32);
		}
		
		public int makeInt() {
			return next(32);
		}
		
		public boolean makeBoolean() {
			return next(1) < next(1);
		}
		
		public char makeChar() {
			return (char) (next(8) >> 48);
		}
		
		public char makeCharStrict() {
			return (char) (MathUtil.secureRandInt(48, 122));
		}
		
		public String makeString(final int length) {
			String s = "";
			for (int i = 0; i < length; i++) {
				s += makeChar();
			}
			return s;
		}
		
		public String makeStringStrict(final int length) {
			String s = "";
			for (int i = 0; i < length; i++) {
				s += makeCharStrict();
			}
			return s;
		}
		
		public byte[] getSeed() {
			return this.seed;
		}
		
		public String getSeedMD5() {
			return MD5Utils.getMD5Hex(this.seed);
		}
	}
	
	public static class nSecureRandom extends SecureRandom {
		
		private final nRandom nr = new nRandom();
		
		public nSecureRandom() {
			super();
		}
		
		public nSecureRandom(final byte[] seed) {
			super(seed);
		}
		
		@Override
		public void nextBytes(byte[] bytes) {
			byte[] data = this.nr.read(bytes.length);
			for (int i = 0; i < bytes.length; i++) {
				bytes[i] = data[i];
			}
		}
		
	}

	public static void main(String[] args) {
		final nRandom nr = new nRandom();
		JVMUtil.async_println("nRandom Seed: " + nr.getSeedMD5());
		JVMUtil.async_println("Reading 32 bytes of random data...");
		final byte[] rng = nr.read(32);
		JVMUtil.async_println("Data.isNull=" + (rng == null));
		for (int i = 0; i < 100; i++) {
			JVMUtil.async_print(nr.makeCharStrict());
		}
	}
}
